import os
import sys
from typing import Tuple, List, Dict
import json
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
    QFrame, QApplication, QGraphicsScene, QGraphicsSceneMouseEvent, QMenu, QGraphicsSceneContextMenuEvent, \
    QGraphicsTextItem
from PyQt5.QtCore import QSize, QCoreApplication, pyqtSignal, QLineF, QObject, QPoint, Qt, QPointF
from PyQt5.QtGui import QIcon, QPixmap, QPen, QColor, QKeyEvent

from pmgwidgets.flowchart.flow_items import CustomRect, CustomPort, CustomLine, CustomMidPoint

COLOR_NORMAL = QColor(212, 227, 242)
COLOR_HOVER = QColor(255, 200, 00)
COLOR_HOVER_PORT = QColor(0, 0, 50)


class PMGraphicsScene(QGraphicsScene):
    signal_item_dragged = pyqtSignal(str)  # 拖拽控件发生的事件。
    signal_port_clicked = pyqtSignal(str)  # 点击端口的事件
    signal_clear_selection = pyqtSignal()  # 清除选择的事件

    def __init__(self, parent=None, graphics_view: 'QGraphicsView' = None, flow_widget: 'PMFlowWidget' = None):
        super().__init__(parent)
        from pmgwidgets.flowchart.flow_items import CustomLine
        self.lines: List[CustomLine] = []
        self.nodes: List['Node'] = []
        self.drawing_lines = False
        self.line_start_port = None
        self.line_start_point = None
        self.line_end_port = None
        self.line = self.addLine(0, 0, 1, 1, QPen())
        self.graphics_view = graphics_view
        self.flow_widget: 'PMFlowWidget' = flow_widget
        self.menu = QMenu()
        self.menu.addAction('New').triggered.connect(lambda x: self.add_node())
        self.menu.addAction('Delete Selected').triggered.connect(lambda x: self.delete_selected_item())
        self.selected_items = []

    def contextMenuEvent(self, event: 'QGraphicsSceneContextMenuEvent') -> None:
        super(PMGraphicsScene, self).contextMenuEvent(event)
        self.menu.exec_(event.screenPos())

    def keyPressEvent(self, event: 'QKeyEvent') -> None:
        if event.key() == Qt.Key_Delete:
            self.delete_selected_item()

    def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
        super(PMGraphicsScene, self).mouseMoveEvent(event)
        if self.drawing_lines:
            self.line.show()
            self.line_end_point = event.scenePos()
            self.line.setLine(QLineF(self.line_end_point, self.line_start_point))

    def connect_port(self, end_port):
        from pmgwidgets.flowchart.flow_items import CustomLine
        self.line.hide()
        line = CustomLine(self.line_start_port, end_port, self)
        line.repaint_callback = lambda: self.graphics_view.viewport().update()
        self.lines.append(line)
        self.addItem(line)
        self.signal_item_dragged.connect(line.refresh)
        self.drawing_lines = False

    def add_node(self):
        view = self.graphics_view

        n = Node(self, 'node3', input_ports=[CustomPort(10), CustomPort(11)],
                 output_ports=[CustomPort(12), CustomPort(13), CustomPort(14)])
        n.set_pos(200, 50)
        print(n.get_pos())
        self.nodes.append(n)


    def delete_selected_item(self):
        '''
        删除被选中的物品
        :return:
        '''
        print(self.selectedItems())
        for selected_item in self.selected_items:
            if hasattr(selected_item, 'on_delete'):
                selected_item.on_delete()
        self.selected_items = []
        print('delete!')

    def unselect_item(self, item):
        if item in self.selected_items:
            self.selected_items.remove(item)

    def select_item(self, item):
        if item not in self.selected_items:
            self.selected_items.append(item)


class Node(QObject):
    '''
    Node是一个节点的抽象
    '''

    def __init__(self, canvas: 'PMGraphicsScene', name, input_ports: List[CustomPort] = None,
                 output_ports: List[CustomPort] = None, content: object = None):
        super(Node, self).__init__()

        self.name = name
        self.base_rect = CustomRect(self)
        self.text = QGraphicsTextItem(parent=self.base_rect)
        self.text.setPos(100, 10)
        self.text.setPlainText(self.name)
        if input_ports == None:
            self.input_ports = [CustomPort(0), CustomPort(1)]
        else:
            self.input_ports = input_ports
        if output_ports == None:
            self.output_ports = [CustomPort(2), CustomPort(3)]
        else:
            self.output_ports = output_ports
        self.canvas = canvas
        self.content = None

        self.setup()

    def set_pos(self, x: int, y: int):
        '''
        设置位置，左上角角点
        :param x:
        :param y:
        :return:
        '''
        self.base_rect.setPos(x, y)
        self.refresh_pos()

    def get_pos(self) -> Tuple[int, int]:
        '''
        获取位置，左上角角点
        :return:
        '''
        pos = self.base_rect.pos()
        return pos.x(), pos.y()

    def refresh_pos(self):
        y = self.base_rect.y()
        dy_input = self.base_rect.boundingRect().height() / (1 + len(self.input_ports))
        dy_output = self.base_rect.boundingRect().height() / (1 + len(self.output_ports))
        x_input = self.base_rect.x() + 5
        x_output = self.base_rect.x() + self.base_rect.boundingRect().width() - 15
        for i, p in enumerate(self.input_ports):
            p.setPos(QPointF(x_input, y + int(dy_input * (1 + i))))
        for i, p in enumerate(self.output_ports):
            p.setPos(QPointF(x_output, y + int(dy_output * (1 + i))))
        self.canvas.signal_item_dragged.emit('')

    def setup(self):
        self.base_rect.setPos(80, 80)
        self.canvas.signal_clear_selection.connect(self.base_rect.on_clear_selection)
        self.canvas.addItem(self.base_rect)

        for p in self.input_ports + self.output_ports:
            self.canvas.addItem(p)
            p.port_clicked.connect(self.on_port_clicked)
        self.refresh_pos()

    def on_port_clicked(self, port: 'CustomPort'):
        if self.canvas.drawing_lines:
            if self.canvas.line_start_port is not port:
                self.canvas.connect_port(port)
            else:
                self.canvas.drawing_lines = False
        else:
            self.canvas.drawing_lines = True
            self.canvas.line_start_point = port.center_pos
            self.canvas.line_start_port = port

    def on_delete(self):
        for port in self.input_ports + self.output_ports:
            port.canvas = self.canvas
            port.on_delete()
        self.canvas.removeItem(self.base_rect)
        self.canvas.nodes.remove(self)
        self.deleteLater()

    def __repr__(self):
        s = super(Node, self).__repr__()
        return s + repr(self.input_ports) + repr(self.output_ports)


class PMFlowWidget(QWidget):
    def __init__(self):
        # from pyminer2.ui.base.widgets.resources import icon_lc_save, icon_sc_shadowcurser, \
        #     icon_lc_connectorlinesarrowend, icon_lc_undo, \
        #     icon_lc_redo, icon_sc_deletepage, icon_lc_aligncenter, icon_lc_alignmiddle, icon_lc_zoomin, icon_lc_zoomout, \
        #     icon_dataprovider, icon_sc_autosum, icon_lc_drawchart, icon_lc_statisticsmenu, icon_lc_dbreportedit
        _translate = QCoreApplication.translate
        super().__init__()
        self.setObjectName("tab_flow")

        self.verticalLayout_6 = QVBoxLayout(self)
        self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_6.setSpacing(0)
        self.verticalLayout_6.setObjectName("verticalLayout_6")
        self.widget_3 = QWidget(self)
        self.widget_3.setMinimumSize(QSize(0, 30))
        self.widget_3.setObjectName("widget_3")

        self.horizontalLayout_6 = QHBoxLayout(self.widget_3)
        self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_6.setSpacing(1)
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.toolButton_4 = QToolButton(self.widget_3)
        icon7 = QIcon()
        icon7.addPixmap(QPixmap(":/pyqt/source/images/NavOverFlow_Start.png"), QIcon.Normal,
                        QIcon.Off)
        self.toolButton_4.setIcon(icon7)
        self.toolButton_4.setIconSize(QSize(25, 25))
        self.toolButton_4.setObjectName("toolButton_4")
        self.horizontalLayout_6.addWidget(self.toolButton_4)
        self.toolButton_3 = QToolButton(self.widget_3)
        icon8 = QIcon()
        icon8.addPixmap(QPixmap(":/pyqt/source/images/lc_basicstop.png"), QIcon.Normal, QIcon.Off)
        self.toolButton_3.setIcon(icon8)
        self.toolButton_3.setIconSize(QSize(25, 25))
        self.toolButton_3.setObjectName("toolButton_3")
        self.horizontalLayout_6.addWidget(self.toolButton_3)
        self.toolButton_6 = QToolButton(self.widget_3)

        # self.toolButton_6.setIcon(icon_lc_save)
        self.toolButton_6.setIconSize(QSize(25, 25))
        self.toolButton_6.setObjectName("toolButton_6")
        self.horizontalLayout_6.addWidget(self.toolButton_6)
        self.toolButton_5 = QToolButton(self.widget_3)

        # self.toolButton_5.setIcon(icon_sc_shadowcurser)
        self.toolButton_5.setIconSize(QSize(25, 25))
        self.toolButton_5.setObjectName("toolButton_5")
        self.horizontalLayout_6.addWidget(self.toolButton_5)
        self.toolButton_2 = QToolButton(self.widget_3)

        # self.toolButton_2.setIcon(icon_lc_connectorlinesarrowend)
        self.toolButton_2.setIconSize(QSize(25, 25))
        self.toolButton_2.setObjectName("toolButton_2")
        self.horizontalLayout_6.addWidget(self.toolButton_2)
        self.toolButton = QToolButton(self.widget_3)

        # self.toolButton.setIcon(icon_lc_undo)
        self.toolButton.setIconSize(QSize(25, 25))
        self.toolButton.setObjectName("toolButton")
        self.horizontalLayout_6.addWidget(self.toolButton)
        self.toolButton_7 = QToolButton(self.widget_3)

        # self.toolButton_7.setIcon(icon_lc_redo)
        self.toolButton_7.setIconSize(QSize(25, 25))
        self.toolButton_7.setObjectName("toolButton_7")
        self.horizontalLayout_6.addWidget(self.toolButton_7)
        self.toolButton_8 = QToolButton(self.widget_3)

        # self.toolButton_8.setIcon(icon_sc_deletepage)
        self.toolButton_8.setIconSize(QSize(25, 25))
        self.toolButton_8.setObjectName("toolButton_8")
        self.horizontalLayout_6.addWidget(self.toolButton_8)
        self.toolButton_9 = QToolButton(self.widget_3)

        # self.toolButton_9.setIcon(icon_lc_aligncenter)
        self.toolButton_9.setIconSize(QSize(25, 25))
        self.toolButton_9.setObjectName("toolButton_9")
        self.horizontalLayout_6.addWidget(self.toolButton_9)
        self.toolButton_10 = QToolButton(self.widget_3)

        # self.toolButton_10.setIcon(icon_lc_alignmiddle)
        self.toolButton_10.setIconSize(QSize(25, 25))
        self.toolButton_10.setObjectName("toolButton_10")
        self.horizontalLayout_6.addWidget(self.toolButton_10)
        self.toolButton_11 = QToolButton(self.widget_3)

        # self.toolButton_11.setIcon(icon_lc_zoomin)
        self.toolButton_11.setIconSize(QSize(25, 25))
        self.toolButton_11.setObjectName("toolButton_11")
        self.horizontalLayout_6.addWidget(self.toolButton_11)
        self.toolButton_12 = QToolButton(self.widget_3)

        # self.toolButton_12.setIcon(icon_lc_zoomout)
        self.toolButton_12.setIconSize(QSize(25, 25))
        self.toolButton_12.setObjectName("toolButton_12")
        self.horizontalLayout_6.addWidget(self.toolButton_12)
        self.toolButton_13 = QToolButton(self.widget_3)

        # self.toolButton_13.setIcon(icon_dataprovider)
        self.toolButton_13.setIconSize(QSize(25, 25))
        self.toolButton_13.setObjectName("toolButton_13")
        self.horizontalLayout_6.addWidget(self.toolButton_13)
        self.toolButton_16 = QToolButton(self.widget_3)

        # self.toolButton_16.setIcon(icon_sc_autosum)
        self.toolButton_16.setIconSize(QSize(25, 25))
        self.toolButton_16.setObjectName("toolButton_16")
        self.horizontalLayout_6.addWidget(self.toolButton_16)
        self.toolButton_17 = QToolButton(self.widget_3)

        # self.toolButton_17.setIcon(icon_lc_drawchart)
        self.toolButton_17.setIconSize(QSize(25, 25))
        self.toolButton_17.setObjectName("toolButton_17")
        self.horizontalLayout_6.addWidget(self.toolButton_17)
        self.toolButton_14 = QToolButton(self.widget_3)

        # self.toolButton_14.setIcon(icon_lc_statisticsmenu)
        self.toolButton_14.setIconSize(QSize(25, 25))
        self.toolButton_14.setObjectName("toolButton_14")
        self.horizontalLayout_6.addWidget(self.toolButton_14)
        self.toolButton_15 = QToolButton(self.widget_3)

        # self.toolButton_15.setIcon(icon_lc_dbreportedit)
        self.toolButton_15.setIconSize(QSize(25, 25))
        self.toolButton_15.setObjectName("toolButton_15")
        self.horizontalLayout_6.addWidget(self.toolButton_15)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.horizontalLayout_6.addItem(spacerItem)
        self.verticalLayout_6.addWidget(self.widget_3)

        self.graphicsView = QGraphicsView(self)

        self.graphicsView.setFrameShape(QFrame.NoFrame)
        self.graphicsView.setObjectName("graphicsView")
        self.verticalLayout_6.addWidget(self.graphicsView)

        self.toolButton_4.setText(_translate("MainWindow", "..."))
        self.toolButton_3.setText(_translate("MainWindow", "..."))
        self.toolButton_6.setText(_translate("MainWindow", "..."))
        self.toolButton_5.setText(_translate("MainWindow", "..."))
        self.toolButton_2.setText(_translate("MainWindow", "..."))
        self.toolButton.setText(_translate("MainWindow", "..."))
        self.toolButton_7.setText(_translate("MainWindow", "..."))
        self.toolButton_8.setText(_translate("MainWindow", "..."))
        self.toolButton_9.setText(_translate("MainWindow", "..."))
        self.toolButton_10.setText(_translate("MainWindow", "..."))
        self.toolButton_11.setText(_translate("MainWindow", "..."))
        self.toolButton_12.setText(_translate("MainWindow", "..."))
        self.toolButton_13.setText(_translate("MainWindow", "..."))
        self.toolButton_16.setText(_translate("MainWindow", "..."))
        self.toolButton_17.setText(_translate("MainWindow", "..."))
        self.toolButton_14.setText(_translate("MainWindow", "..."))
        self.toolButton_15.setText(_translate("MainWindow", "..."))

        # self.rect = CustomRect()
        # self.rect.setPos(50, 50)
        # self.rect2 = CustomRect()
        # self.rect2.setPos(100, 100)
        #
        self.scene = PMGraphicsScene(graphics_view=self.graphicsView, flow_widget=self)
        self.scene.setSceneRect(0, 0, 300, 300)

        # self.scene.addItem(self.rect)
        # self.scene.addItem(self.rect2)
        #
        # self.cl = CustomLine()
        # self.cl.start_pos = (0, 30)
        # self.cl.end_pos = (100, 100)
        # self.scene.addItem(self.cl)
        def create_nodes(self):
            self.n = Node(self.scene, 'node1', input_ports=[CustomPort(0), CustomPort(1)],
                          output_ports=[CustomPort(2), CustomPort(3), CustomPort(4)])
            self.n.set_pos(200, 50)
            print(self.n.get_pos())
            self.n2 = Node(self.scene, 'node2', input_ports=[CustomPort(5), CustomPort(6)],
                           output_ports=[CustomPort(7), CustomPort(8), CustomPort(9)])

            self.nodes = [self.n, self.n2]

        self.nodes: List[Node] = self.scene.nodes
        self.lines = self.scene.lines

        self.load_flowchart()
        # create_nodes(self)
        print(self.nodes)
        self.graphicsView.setScene(self.scene)
        print(self.find_port(1))
        # self.dump_flowchart()

    def find_port(self, port_id: int):
        for n in self.nodes:
            for p in n.input_ports + n.output_ports:
                if p.id == port_id:
                    return p
        return None

    def dump_flowchart(self):
        fc_info = {}
        connections = []
        nodes_dic = {}
        fc_info['nodes'] = nodes_dic
        fc_info['connections'] = connections
        for line in self.lines:
            line_properties = {}
            start_id = line.start_port.id
            end_id = line.end_port.id
            line_properties['start_id'] = start_id
            line_properties['end_id'] = end_id
            mid_positions = line.get_central_points_positions()
            # for point in line.center_points:
            #     mid_positions.append(point.pos())
            line_properties['mid_positions'] = mid_positions
            connections.append(line_properties)
        print(self.nodes)
        for node in self.nodes:
            node_properties = {}
            node_properties['name'] = node.name
            node_properties['pos'] = node.get_pos()
            input_ports_dic = {}
            output_ports_dic = {}
            for port in node.input_ports:
                input_ports_dic[port.id] = {'id': port.id, 'pos': port.get_pos(), 'contents': {}}
            for port in node.output_ports:
                output_ports_dic[port.id] = {'id': port.id, 'pos': port.get_pos(), 'contents': {}}
            node_properties['input_ports'] = input_ports_dic
            node_properties['output_ports'] = output_ports_dic
            nodes_dic[node.name] = node_properties
        with open(r'flowchart_stat.pmcache', 'w') as f:
            json.dump(fc_info, f, indent=4)
        pass

    def load_flowchart(self, path=''):
        file = r'flowchart_stat.pmcache'
        if not os.path.exists(file):
            return
        with open(r'flowchart_stat.pmcache', 'r') as f:
            text = f.read()
            fc_info_dic: Dict[str, Dict] = json.loads(text)
            nodes_dic = fc_info_dic['nodes']
            connections: List = fc_info_dic['connections']
        for k in nodes_dic.keys():
            node_property = nodes_dic[k]
            node_name = node_property['name']
            node_pos = node_property['pos']
            input_ports = []
            for input_port_id in node_property['input_ports'].keys():
                port_property = node_property['input_ports'][input_port_id]
                port = CustomPort(port_id=int(input_port_id), content=port_property['contents'])
                input_ports.append(port)
            output_ports = []
            for output_port_id in node_property['output_ports'].keys():
                port_property = node_property['output_ports'][output_port_id]
                port = CustomPort(port_id=int(output_port_id), content=port_property['contents'])
                output_ports.append(port)

            node = Node(self.scene, node_name, input_ports=input_ports, output_ports=output_ports)
            node.set_pos(*node_pos)
            self.nodes.append(node)
        for line_property in connections:
            start_id, end_id = line_property['start_id'], line_property['end_id']
            print(start_id, end_id, self.nodes)
            start_port, end_port = self.find_port(start_id), self.find_port(end_id)
            mid_positions = line_property['mid_positions']
            mid_points = []
            for pos in mid_positions:
                mid_points.append(CustomMidPoint(pos=QPointF(*pos)))
            print(start_port, end_port, start_id, end_id)

            line = CustomLine(canvas=self.scene, start_port=start_port, end_port=end_port, mid_points=mid_points)
            # line = CustomLine(self.line_start_port, end_port, self)
            line.repaint_callback = lambda: self.scene.graphics_view.viewport().update()
            # self.lines.append(line)
            self.scene.addItem(line)
            self.scene.signal_item_dragged.connect(line.refresh)
            # self.drawing_lines = False
            self.lines.append(line)

    def closeEvent(self, a0: 'QCloseEvent') -> None:
        self.dump_flowchart()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    graphics = PMFlowWidget()
    graphics.show()

    sys.exit(app.exec_())
